/**
 *  This file is part of PennerBot
 *
 *  Copyright (C) 2010 microkernel
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <vector>

#include <core/anticaptcha.hpp>
#include <core/bot.hpp>
#include <core/configparser.hpp>
#include <core/convert.hpp>
#include <core/portable.hpp>
#include <core/strings.hpp>
#include <core/version.hpp>

#include <console/mainhandler.hpp>

namespace PBot
{
namespace Console
{

Main::Main()
 : running(true), listener(StatusListener::GetInstance()), log("main", mutex), updateCheck(NULL), numActiveBots(0)
{
    curl_global_init(CURL_GLOBAL_ALL);
}

Main::~Main()
{
    for(std::vector<Core::Bot*>::iterator i(botList.begin()), end(botList.end()); i != end; ++i)
    {
        delete *i;
    }

    delete updateCheck;

    Core::AntiCaptcha::GetInstance().Destroy();
    Core::Strings::GetInstance().Destroy();
    StatusListener::GetInstance().Destroy();
}

int Main::Start()
{
    log.Info("PennerBot " + std::string(__PBOT_CONSOLE_VERSION__) + " started");

#ifdef PBOT_SPECIAL

    log.Info("---Special Version---");

#endif /* PBOT_SPECIAL */

    log.Info("Copyright 2010 by microkernel");
    log.Info("This program comes with ABSOLUTELY NO WARRANTY.");
    log.Info("This is free software, and you are welcome to redistribute it under certain conditions;");
    log.Info("read LICENSE.txt for details.");
    log.Info("It is for demonstration purposes only and the usage of this tool is not recommended.");
    log.Info("The developers disassociate themselves from the abuse of this tool.");

    try
    {
        log.Info("Trying to load config file");
        LoadFromFile();
        log.Info("Checking for updates");

        if(updateCheck->GetVersionStatus() == Core::UpdateCheck::UNSAFE)
        {
            running = false;
        }


        log.Info("Creating threads");
        unsigned int numThreads = CreateThreads();
        log.Info(Core::NumToStr(numThreads) + " threads created");

        while(running)
        {
            if(numActiveBots == 0)
            {
                running = false;
                continue;
            }

            if(updateCheck->GetVersionStatus() == Core::UpdateCheck::UNSAFE)
            {
                running = false;
                break;
            }

            PortableMicroSleep(100);
        }
    }
    catch(...)
    {
        log.Error("Critical error occured. Terminating.");
        return 1;
    }

    for(std::vector<Core::Bot*>::iterator i(botList.begin()), end(botList.end()); i != end; ++i)
    {
        (*i)->Stop();
    }

    for(std::vector<bthread>::iterator i(threads.begin()), end(threads.end()); i != end; ++i)
    {
        (*i)->join();
    }

    PortableMicroSleep(500);

    listener.RequestStop();

    log.Info("Everything finished. Terminating.");

    return 0;
}

void Main::Stop()
{
    log.Info("Stop requested - waiting for remaining actions to finish");
    running = false;
}

void Main::LoadFromFile()
{
    Core::ConfigParser parser;

    std::vector<Core::Config*> cfgs;


    switch(parser.LoadFromFile("config.xml", cfgs))
    {
        case Core::ConfigParser::ALL_CLEAR:
            log.Info("Config file loaded");
            break;

        case Core::ConfigParser::FILE_OPEN_ERROR:
            log.Error("Could not open config file");
            throw std::exception();

        case Core::ConfigParser::FILE_NOT_SUPPORTED:
            log.Error("Config file format not supported");
            throw std::exception();

        case Core::ConfigParser::MISSING_USERNAME:
            log.Error("Missing username in config file");
            throw std::exception();

        case Core::ConfigParser::MISSING_PASSWORD:
            log.Error("Missing password in config file");
            throw std::exception();

        case Core::ConfigParser::MISSING_USERAGENT:
            log.Error("Missing user-agent in config file");
            throw std::exception();

        case Core::ConfigParser::MISSING_GAME:
            log.Error("Missing game in config file");
            throw std::exception();
    }

    int numberOfBots = cfgs.size();

    for(int i=0; i < numberOfBots; ++i)
    {
        Core::Bot* bot = new Core::Bot(cfgs[i]);
        bots.push(bot);
        botList.push_back(bot);
    }

    updateCheck = new Core::UpdateCheck(numberOfBots);

    cfgs.push_back(updateCheck->GetConfig());

    numActiveBots = numberOfBots;

    log.Info(Core::NumToStr(numberOfBots)+" bots created");


    listener.Initialize(cfgs);

}

unsigned int Main::CreateThreads()
{
    unsigned int i;
    for(i=0; i < bots.size() && i < 4; ++i)
    {
        boost::shared_ptr<boost::thread> thread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&Main::ThreadDo, this)));
        threads.push_back(thread);

    }

    PortableMicroSleep(100);

    listener.StartListener();

    return i;
}

void Main::ThreadDo()
{
    Core::Bot* bot;

    unsigned int numActiveBotsMT;
    bool empty;

    {
        boost::recursive_mutex::scoped_lock lock(mutex);
        numActiveBotsMT = numActiveBots;
    }

    while(numActiveBotsMT > 0)
    {
        {
            boost::recursive_mutex::scoped_lock lock(mutex);
            empty = bots.empty();
        }

        if(empty)
        {
            PortableMicroSleep(100);
            {
                boost::recursive_mutex::scoped_lock lock(mutex);
                numActiveBotsMT = numActiveBots;
            }
            continue;
        }

        {
            boost::recursive_mutex::scoped_lock lock(mutex);
            bot = bots.front();
            bots.pop();
        }

        // do something with bot
        bot->Do();

        if(bot->IsActive())
        {
            boost::recursive_mutex::scoped_lock lock(mutex);
            bots.push(bot);
        }
        else
        {
            boost::recursive_mutex::scoped_lock lock(mutex);
            --numActiveBots;
        }

        PortableMicroSleep(100);

        {
            boost::recursive_mutex::scoped_lock lock(mutex);
            numActiveBotsMT = numActiveBots;
        }
    }
}

}
}
